﻿

using HBase;
using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace hconnect {
    /// <summary>
    /// 一个二次封装了的OPC UA库，支持从opc ua服务器读写节点数据，批量读写，订阅，批量订阅，历史数据读取，方法调用操作。
    /// </summary>
    public class OpcUaClient :SocketZtTaskConnect {
        public OpcUaClient() : base() {
            dic_subscriptions = new Dictionary<string ,Subscription>();
            var certificateValidator = new CertificateValidator();
            certificateValidator.CertificateValidation += (sender ,eventArgs) => {
                if( ServiceResult.IsGood(eventArgs.Error) )
                    eventArgs.Accept = true;
                else if( eventArgs.Error.StatusCode.Code == StatusCodes.BadCertificateUntrusted )
                    eventArgs.Accept = true;
                else
                    throw new Exception(string.Format("Failed to validate certificate with error code {0}: {1}" ,eventArgs.Error.Code ,eventArgs.Error.AdditionalInfo));
            };
            SecurityConfiguration securityConfigurationcv = new SecurityConfiguration {
                AutoAcceptUntrustedCertificates = true ,
                RejectSHA1SignedCertificates = false ,
                MinimumCertificateKeySize = 1024 ,
            };
            certificateValidator.Update(securityConfigurationcv);
            // Build the application configuration
            application = new ApplicationInstance {
                ApplicationType = ApplicationType.Client ,
                ConfigSectionName = OpcUaName ,
                ApplicationConfiguration = new ApplicationConfiguration {
                    ApplicationName = OpcUaName ,
                    ApplicationType = ApplicationType.Client ,
                    CertificateValidator = certificateValidator ,
                    ServerConfiguration = new ServerConfiguration {
                        MaxSubscriptionCount = 100000 ,
                        MaxMessageQueueSize = 1000000 ,
                        MaxNotificationQueueSize = 1000000 ,
                        MaxPublishRequestCount = 10000000 ,
                    } ,
                    SecurityConfiguration = new SecurityConfiguration {
                        AutoAcceptUntrustedCertificates = true ,
                        RejectSHA1SignedCertificates = false ,
                        MinimumCertificateKeySize = 1024 ,
                    } ,
                    TransportQuotas = new TransportQuotas {
                        OperationTimeout = 6000000 ,
                        MaxStringLength = int.MaxValue ,
                        MaxByteStringLength = int.MaxValue ,
                        MaxArrayLength = 65535 ,
                        MaxMessageSize = 419430400 ,
                        MaxBufferSize = 65535 ,
                        ChannelLifetime = -1 ,
                        SecurityTokenLifetime = -1
                    } ,
                    ClientConfiguration = new ClientConfiguration {
                        DefaultSessionTimeout = -1 ,
                        MinSubscriptionLifetime = -1 ,
                    } ,
                    DisableHiResClock = true
                }
            };
            m_configuration = application.ApplicationConfiguration;
        }
        protected override bool reConnect() {
            if( !m_IsConnected ) {
                Connect("opc.tcp://" + ip + ":" + port);
            }
            return true;
        }
        #region Connect And Disconnect


        /// <summary>
        /// Creates a new session.
        /// </summary>
        /// <returns>The new session object.</returns>
        private async Task<Session> Connect(string serverUrl) {
            // disconnect from existing session.
            forceDisConnection();
            if( m_configuration == null ) {
                throw new ArgumentNullException("m_configuration");
            }
            // select the best endpoint.opc.tcp://192.168.10.115:4840
            EndpointDescription endpointDescription = CoreClientUtils.SelectEndpoint("opc.tcp://192.168.10.115:4840" ,UseSecurity);
            EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(m_configuration);
            ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null ,endpointDescription ,endpointConfiguration);
            m_session = await Session.Create(
               m_configuration ,
               endpoint ,
               false ,
               false ,
               (string.IsNullOrEmpty(OpcUaName)) ? m_configuration.ApplicationName : OpcUaName ,
               60000 ,
               UserIdentity ,
               new string[] { });
            // set up keep alive callback.
            m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive);
            // update the client status
            m_IsConnected = true;

            return m_session;
        }
        /// <summary>
        /// Disconnects from the server.
        /// </summary>
        protected override void forceDisConnection() {
            LoggerCommon.fileAll("Disconnected" + ip);
            // stop any reconnect operation.
            if( m_reconnectHandler != null ) {
                m_reconnectHandler.Dispose();
                m_reconnectHandler = null;
            }
            // disconnect any existing session.
            if( m_session != null ) {
                m_session.Close(10000);
                m_session = null;
            }
            // update the client status
            m_IsConnected = false;
        }
        #endregion
        #region Event Handlers
        /// <summary>
        /// Handles a keep alive event from a session.
        /// </summary>
        private void Session_KeepAlive(Session session ,KeepAliveEventArgs e) {
            // check for events from discarded sessions.
            if( !Object.ReferenceEquals(session ,m_session) ) {
                return;
            }
            // start reconnect sequence on communication error.
            if( ServiceResult.IsBad(e.Status) ) {
                if( m_reconnectPeriod <= 0 ) {
                    LoggerCommon.fileAll(String.Format("Communication Error ({0})" ,e.Status));
                    return;
                }
                LoggerCommon.fileAll(String.Format("Reconnecting in {0}s" ,m_reconnectPeriod));
                if( m_reconnectHandler == null ) {
                    m_reconnectHandler = new SessionReconnectHandler();
                    m_reconnectHandler.BeginReconnect(m_session ,m_reconnectPeriod * 1000 ,Server_ReconnectComplete);
                }
                return;
            }
            LoggerCommon.fileAll(String.Format("Connected [{0}]" ,session.Endpoint.EndpointUrl));
        }
        /// <summary>
        /// Handles a reconnect event complete from the reconnect handler.
        /// </summary>
        private void Server_ReconnectComplete(object sender ,EventArgs e) {
            // ignore callbacks from discarded objects.
            if( !Object.ReferenceEquals(sender ,m_reconnectHandler) ) {
                return;
            }
            m_session = m_reconnectHandler.Session;
            m_reconnectHandler.Dispose();
            m_reconnectHandler = null;
        }
        #endregion
        #region Public Members
        /// <summary>
        /// a name of application name show on server
        /// </summary>
        public string OpcUaName { get; set; } = "Opc Ua Helper";
        /// <summary>
        /// Whether to use security when connecting.
        /// </summary>
        public bool UseSecurity {
            get { return m_useSecurity; }
            set { m_useSecurity = value; }
        }
        /// <summary>
        /// The user identity to use when creating the session.
        /// </summary>
        public IUserIdentity UserIdentity { get; set; }
        /// <summary>
        /// Indicate the connect status
        /// </summary>
        public bool Connected {
            get { return m_IsConnected; }
        }
        #endregion
        #region Private Fields
        private ApplicationInstance application;
        private ApplicationConfiguration m_configuration;
        protected Session m_session;
        private bool m_IsConnected;                       //是否已经连接过
        private int m_reconnectPeriod = 10;               // 重连状态
        private bool m_useSecurity;
        private SessionReconnectHandler m_reconnectHandler;
        private Dictionary<string ,Subscription> dic_subscriptions;        // 系统所有的节点信息
        #endregion
    }
}
